/**
  HomeMade by shoe[box]
 
  Redistribution and use in source and binary forms, with or without 
  modification, are permitted provided that the following conditions are
  met:

  * Redistributions of source code must retain the above copyright notice, 
    this list of conditions and the following disclaimer.
  
  * Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions and the following disclaimer in the 
    documentation and/or other materials provided with the distribution.
  
  * Neither the name of shoe[box] nor the names of its 
    contributors may be used to endorse or promote products derived from 
    this software without specific prior written permission.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
  IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 
  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package org.shoebox.guacamole.view.abstracts {

	import org.shoebox.guacamole.core.RenderTask;
	import org.shoebox.guacamole.interfaces.IRenderable;
	import org.shoebox.guacamole.interfaces.IZSortable;
	import org.shoebox.guacamole.materials.abstracts.AMaterial;
	import org.shoebox.guacamole.view.GuaScene;
	import org.shoebox.guacamole.view.containers.GuaContainer;
	import org.shoebox.utils.logger.Logger;
	import org.shoebox.utils.system.Signal;
	import flash.errors.IllegalOperationError;
	import flash.geom.Matrix3D;
	import flash.geom.Rectangle;
	import flash.geom.Vector3D;
	
	/**
	* org.shoebox.guacamole.view.AProjectable
	* @author shoebox
	*/
	public class GuaProjectable extends Signal implements IZSortable , IRenderable{
		
		public var parent				: GuaProjectable = null;
		public var container			: GuaContainer = null;
		public var material			: AMaterial = null;
		public var scene				: GuaScene;
		
		protected var _bInitialized	: Boolean;
		protected var _bCustomPivot		: Boolean = false;
		protected var _bInvalidateProjVertices		: Boolean;
		protected var _bInvalidate		: Boolean;
		protected var _iWidth			: int;
		protected var _iHeight			: int;
		protected var _iDepth			: int = 1;
		protected var _vRotations		: Vector.<Number>;
		protected var _vScales			: Vector.<Number>;
		protected var _vPosition		: Vector.<Number>;
		protected var _oMatrix			: Matrix3D;
		protected var _mProjection		: Matrix3D;
		protected var _mResult			: Matrix3D;
		protected var _rBounds			: Rectangle;
		protected var _vPivot			: Vector3D = new Vector3D(  0 , 0 , 0 );
		
		// -------o constructor
		
			public function GuaProjectable( ) : void {
				
				_mProjection	= new Matrix3D( );
				_mResult		= new Matrix3D( );
				_oMatrix 		= new Matrix3D( );
				_rBounds 		= new Rectangle( );
				_vPosition 		= Vector.<Number>( [ 0 , 0 ] );
				_vRotations 	= Vector.<Number>( [ 0 , 0 , 0 ] );
				_vScales		= Vector.<Number>( [ 1 , 1 , 1 ] );
				
			}

		// -------o public
			
			/**
			* init function
			* @public
			* @param 
			* @return
			*/
			public function init() : void {
				_bInitialized = true;
			}
			
			/**
			* get scaleX function
			* @public
			* @param 
			* @return
			*/
			final public function get scaleX() : Number {
				return _vScales[ 0 ];
			}
			
			/**
			* get scaleX function
			* @public
			* @param 
			* @return
			*/
			final public function get scaleY() : Number {
				return _vScales[ 1 ];
			}
			
			/**
			* appendScale function
			* @public
			* @param 
			* @return
			*/
			final public function appendScale( ax : Number = 1.0 , ay : Number = 1.0 ) : void {
				_vScales[ 0 ] *= ax;
				_vScales[ 1 ] *= ay;
				_bInvalidate = true;
			}
			
			/**
			* project function
			* @public
			* @param 
			* @return
			*/
			final public function getProjectedMatrix( oCam : Matrix3D ) : Matrix3D {
				
				_mResult.identity( );
				_mResult.append( getProjection( ) );
				
				//
					if( parent )
						if( !( parent is GuaScene ))
							_mResult.append( parent.getProjection( ) );
					
				//			
					_mResult.append( oCam );
					
				
				return _mResult;
				
			}
			
			/**
			* getReducedMatrix function
			* @public
			* @param 
			* @return
			*/
			final public function getReducedMatrix() : Matrix3D {
				 var raw:Vector.<Number> = getProjection( ).rawData;
        				raw[3] = 0; // Remove translation.
        				raw[7] = 0;
        				raw[11] = 0;
        				raw[15] = 1;
        				raw[12] = 0;
        				raw[13] = 0;
        				raw[14] = 0;
        		var reducedTransform:Matrix3D = new Matrix3D();
        			reducedTransform.copyRawDataFrom(raw);
        		return reducedTransform;
   
			}
			
			/**
			* getSpaceMatrix function
			* @public
			* @param 
			* @return
			*/
			final public function getProjection() : Matrix3D {
				
				if( !_bInvalidate )
					return _mProjection;
				
				
				_mProjection.identity( );
				if( _bCustomPivot )
					_mProjection.appendTranslation( -_vPivot.x , -_vPivot.y , -_vPivot.z );
					
				if( _vScales[ 0 ] !== 1 || _vScales[ 1 ] !== 1 )
					_mProjection.appendScale( _vScales[ 0 ] , _vScales[ 1 ] , 1 );
				
				if( _vRotations[ 0 ] !== 0 )
					_mProjection.appendRotation( _vRotations[ 0 ] , Vector3D.X_AXIS );
					
				if( _vRotations[ 1 ] !== 0 )
					_mProjection.appendRotation( _vRotations[ 1 ] , Vector3D.Y_AXIS );
					
				if( _vRotations[ 2 ] !== 0 )
					_mProjection.appendRotation( _vRotations[ 2 ] , Vector3D.Z_AXIS );
				
				_mProjection.appendTranslation( _vPosition[0] , _vPosition[1] , 1 );
				return _mProjection;
			}
			
			/**
			* rotate function
			* @public
			* @param 
			* @return
			*/
			final public function rotate( dx : Number , dy : Number , dz : Number ) : void {
				
				_vRotations[ 0 ] += dx;			
				_vRotations[ 1 ] += dy;			
				_vRotations[ 2 ] += dz;
				_bInvalidate = true;	
				
			}
			
			/**
			* setRotation function
			* @public
			* @param 
			* @return
			*/
			final public function setRotation( dx : Number , dy : Number , dz : Number ) : void {
				_vRotations[ 0 ] = dx;
				_vRotations[ 1 ] = dy;
				_vRotations[ 2 ] = dz;
				_bInvalidate = true;	
			}
			
			
			/**
			* getMatrix function
			* @public
			* @param 
			* @return
			*/
			public function getMatrix() : Matrix3D {
				
				if( _bInvalidate )
					updateTransform( );
				
				return _oMatrix;
			}
			
			
			/**
			* updateTransform function
			* @public
			* @param 
			* @return
			*/
			final public function updateTransform() : void {
				
				_oMatrix.identity( );
				_oMatrix.appendTranslation( _vPosition[ 0 ] - _vPivot.x * _vScales[ 0 ] , _vPosition[ 1 ] - _vPivot.y * _vScales[ 1 ] , _vPosition[ 2 ] - _vPivot.z );
				_bInvalidate = false;
			}
			
			
			/**
			* setMaterial function
			* @public
			* @param 
			* @return
			*/
			final public function setMaterial( material : AMaterial ) : void {
				this.material = material;
			}
			
			/**
			* setSize function
			* @public
			* @param 
			* @return
			*/
			public function setSize( w : int , h : int ) : void {
				_bInvalidate = true;
				_rBounds.width  = _iWidth  = w;
				_rBounds.height = _iHeight = h;
			}
			
			/**
			* getWidth function
			* @public
			* @param 
			* @return
			*/
			final public function getWidth() : int {
				return _iWidth;
			}
			
			/**
			* getHeight function
			* @public
			* @param 
			* @return
			*/
			final public function getHeight() : int {
				return _iHeight;
			}
			
			/**
			* setPosition function
			* @public
			* @param 
			* @return
			*/
			final public function setPositionVector( v : Vector.<Number> , vPivot : Vector3D = null ) : void {
				_bInvalidate = true;
				_vPosition = v;
				if( vPivot )
					setPivot( vPivot.x , vPivot.y );
			}
			
			/**
			* setPosition function
			* @public
			* @param 
			* @return
			*/
			final public function setPosition( dx : int , dy : int ) : void {
				_bInvalidateProjVertices = true;
				_bInvalidate = true;
				_vPosition[ 0 ] = dx;
				_vPosition[ 1 ] = dy;
			}
			
			/**
			* setPositionAndVector function
			* @public
			* @param 
			* @return
			*/
			final public function setPosAndPivot( pivotX : int , pivotY : int , dx : int , dy : int ) : void {
				setPivot( pivotX , pivotY );
				setPosition( dx , dy );
			}
			
			/**
			* getBounds function
			* @public
			* @param 
			* @return
			*/
			final public function getBounds() : Rectangle {
				_rBounds.x = _vPosition[ 0 ] - _vPivot.x;
				_rBounds.y = _vPosition[ 1 ] - _vPivot.y;
				return _rBounds;
			}
			
			/**
			* Getter / Setter of the X space position
			* @public
			* @param 
			* @return
			*/
			final public function get x() : Number {
				return _vPosition[ 0 ];
			}
			
			final public function set x( i : Number ) : void {
				_vPosition[ 0 ] = i;
				_bInvalidate = true;
			}
			
			/**
			* Getter / Setter of the Y space position
			* @public
			* @param 
			* @return
			*/
			final public function get y() : Number {
				return _vPosition[ 1 ];
			}
			
			final public function set y( i : Number ) : void {
				_vPosition[ 1 ] = i;
				_bInvalidate = true;
			}
			
			/**
			* Getter / Setter of the Z space position
			* @public
			* @param 
			* @return
			*/
			final public function get z() : Number {
				return _vPosition[ 2 ];
			}
			
			final public function set z( i : Number ) : void {
				_vPosition[ 2 ] = i;
				_bInvalidate = true;
			}
			
			/**
			* Setter / Getter of the application depth
			* 
			* @public
			* @param	i : new depth of the <code>AProjectable</code> item 
			* @return	void
			*/
			public function set depth( i : int ) : void {
				if( parent is GuaContainer )
					( parent as GuaContainer ).zSort( );
					
				_iDepth = i;
			}
			
			public function get depth() : int {
				return _iDepth;
			}
			
			/**
			* setPivot function
			* @public
			* @param 
			* @return
			*/
			final public function setPivot( dx : int , dy : int ) : void {
				
				_vPivot.x = dx;
				_vPivot.y = dy;
				_bInvalidate = true;
				_bCustomPivot = true;
			}
			
			/**
			* setScale function
			* @public
			* @param 
			* @return
			*/
			final public function setScale( x : Number , y : Number ) : void {
				_vScales[ 0 ] = x;
				_vScales[ 1 ] = y;
				_bInvalidate = true;
			}
			
			/**
			* get position function
			* @public
			* @param 
			* @return
			*/
			final public function get position() : Vector.<Number> {
				return _vPosition;
			}
			
			/**
			* move function
			* @public
			* @param 
			* @return
			*/
			public function move( dx : Number , dy : Number , dz : Number = 0 ) : void {
				_vPosition[ 0 ] += dx;
				_vPosition[ 1 ] += dy;
				_vPosition[ 2 ] += dz;
				_bInvalidate = true;
				
			}
			
			
			/**
			* getDepth function
			* @public
			* @param 
			* @return
			*/
			final public function getDepth() : int {
				return _iDepth;
			}
			
			/**
			* render function
			* @public
			* @param 
			* @return
			*/
			public function render( oTask : RenderTask ) : void {
				throw new IllegalOperationError( );
			}
			
		// -------o protected

		// -------o misc

			public static function trc( ...args : Array ) : void {
				Logger.log(GuaProjectable, args);
			}
	}
}
